package util

import (
	"bytes"
	"context"
	"fmt"
	"gfrest/pkg/empty"
	"io"
	"os"
	"os/exec"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/gogf/gf/container/garray"
	"github.com/gogf/gf/os/glog"
)

var (
	// Timeout 执行外部命令超时时间
	Timeout = 60 * 60 * time.Second
)

func copyAndCapture(w io.Writer, r io.Reader, bytesCh chan []byte) ([]byte, error) {
	var out []byte
	buf := make([]byte, 1024, 1024)
	for {
		n, err := r.Read(buf[:])
		if n > 0 {
			d := buf[:n]
			out = append(out, d...)
			_, err := w.Write(d)
			bytesCh <- d
			if err != nil {
				return out, err
			}
		}
		if err != nil {
			// Read returns io.EOF at the end of file, which is not an error for us
			if err == io.EOF {
				err = nil
			}
			return out, err
		}
	}
}

// CmdWtihCh CmdWtihCh
func CmdWtihCh(bytesCh, bytesChErr chan []byte, name string, args ...string) {
	cmd := exec.Command(name, args...)
	glog.Info(cmd.Args)
	var stdout, stderr []byte
	var errStdout, errStderr error
	stdoutIn, _ := cmd.StdoutPipe()
	stderrIn, _ := cmd.StderrPipe()
	err := cmd.Start()
	if err != nil {
		glog.Errorf("cmd.Start() failed with '%s'\n", err)
	}

	// cmd.Wait() should be called only after we finish reading
	// from stdoutIn and stderrIn.
	// wg ensures that we finish
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		stdout, errStdout = copyAndCapture(os.Stdout, stdoutIn, bytesCh)
		wg.Done()
	}()

	stderr, errStderr = copyAndCapture(os.Stderr, stderrIn, bytesChErr)

	wg.Wait()

	err = cmd.Wait()
	if err != nil {
		glog.Errorf("cmd.Run() failed with %s\n", err)
	}
	if errStdout != nil || errStderr != nil {
		glog.Errorf("failed to capture stdout or stderr\n")
	}
	outStr, errStr := string(stdout), string(stderr)
	fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr)
}

// Command with timeout
func Command(name string, arg ...string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), Timeout)
	defer cancel()

	cmd := exec.CommandContext(ctx, name, arg...)

	var buf bytes.Buffer
	cmd.Stdout = &buf
	cmd.Stderr = &buf

	if err := cmd.Start(); err != nil {
		return string(buf.Bytes()), err
	}

	if err := cmd.Wait(); err != nil {
		return string(buf.Bytes()), err
	}

	return string(buf.Bytes()), nil
}

// CommandWithCancel CommandWithCancel
func CommandWithCancel(ctx context.Context, name string, arg ...string) (string, error) {

	cmd := exec.CommandContext(ctx, name, arg...)

	var buf bytes.Buffer
	cmd.Stdout = &buf
	cmd.Stderr = &buf

	if err := cmd.Start(); err != nil {
		// log.Error(ctx, err)
		return string(buf.Bytes()), err
	}

	if err := cmd.Wait(); err != nil {
		// log.Error(ctx, err)
		return string(buf.Bytes()), err
	}

	return string(buf.Bytes()), nil
}

// ContainEmptyId ContainEmptyId
func ContainEmptyId(u interface{}) bool {
	v := reflect.ValueOf(u)
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {

		if strings.Contains(t.Field(i).Name, "Id") {
			if empty.IsEmpty(v.Field(i).Interface()) {
				return true
			}

		}
		// values[t.Field(i).Name] = v.Field(i).Interface().(int)
	}
	return false
}

// StructKeys StructKeys
func StructKeys(u interface{}) []string {
	keys := make([]string, 0, 8)
	v := reflect.ValueOf(u)
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		keys = append(keys, t.Field(i).Name)
	}
	return keys
}

// StructTags StructTags
func StructTags(u interface{}) []string {
	tags := make([]string, 0, 8)
	v := reflect.ValueOf(u)
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		tags = append(tags, t.Field(i).Tag.Get("json"))
	}
	return tags
}

// StructValidTags StructValidTags
func structValidTagsOfReflect(typ reflect.Type) map[string]string {
	tags := make(map[string]string, 8)
	v := reflect.New(typ).Elem()
	t := typ
	for i := 0; i < v.NumField(); i++ {
		v := t.Field(i).Tag.Get("v")

		items := strings.Split(v, "@")
		if len(items) < 3 {
			continue
		}

		// fmt.Println("items", items)
		// fmt.Println("tags", tags)
		// fmt.Println("v", v)
		tags[strings.Trim(items[0], " ")] = items[1]
	}
	return tags
}

// StructValidTags StructValidTags
func StructValidTags(u interface{}) map[string]string {
	tags := make(map[string]string, 8)
	v := reflect.ValueOf(u)
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		// g.Log().Info(t.Field(i).Name)
		// g.Log().Info(g.Export(t.Field(i).Tag))
		if t.Field(i).Type.Kind() == reflect.Struct {
			res := structValidTagsOfReflect(t.Field(i).Type) // recur
			for key := range res {
				tags[key] = res[key]
			}
		} else {
			v := t.Field(i).Tag.Get("v")
			items := strings.Split(v, "@")
			// g.Log().Info(items)
			// g.Log().Info(v)
			if len(items) < 2 {
				continue
			}
			tags[strings.Trim(items[0], " ")] = items[1]
		}

	}
	return tags
}

// FilterMapKeys FilterMapKeys
func FilterMapKeys(m map[string]interface{}, keys []string) map[string]interface{} {
	Keys := garray.NewStrArrayFrom(keys)
	res := make(map[string]interface{}, 8)
	for key, value := range m {
		if Keys.Contains(key) {
			if strings.Contains(key, "_id") && empty.IsEmpty(value) {
				value = nil
			}
			res[key] = value
		}
	}
	return res
}
